package com.googlecode.cpbi.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.packageadmin.PackageAdmin;
import org.springframework.util.StringUtils;

/**
 * @author Jeremy Wales
 */
public class OsgiUtils {
	public static final Bundle UNINSTALLED_BUNDLE = null;
	public static final char PACKAGE_DELIMITER = ',';
	public static final char ATTRIBUTE_DELIMITER = ';';

	private static final Log LOG = LogFactory.getLog(OsgiUtils.class);

	private OsgiUtils() {
	}

	public static boolean isBundle(JarInputStream jarInputStream, URL jarLocation) throws IOException {
		Manifest manifest = JarUtils.getManifestFrom(jarInputStream, jarLocation.toString());
		return StringUtils.hasText(new BundleManifest(manifest).getSymbolicName());
	}

	public static String[] getBootDelegationPackagePatternsFrom(BundleContext bundleContext) {
		String bootDelegationValue = bundleContext.getProperty(Constants.FRAMEWORK_BOOTDELEGATION);
		return StringUtils.addStringToArray(StringUtils.commaDelimitedListToStringArray(bootDelegationValue), "java.*");
	}

	public static Bundle tryToInstallBundleFrom(String bundleLocation, BundleContext bundleContext) {
		try {
			return installBundleFrom(FileUtils.getInputStreamFrom(bundleLocation), bundleLocation, bundleContext);
		} catch (Exception e) {
			LOG.error("Could not install bundle from '" + bundleLocation + "'.", e);
			return UNINSTALLED_BUNDLE;
		}
	}

	public static void tryToStart(Bundle bundle) {
		try {
			bundle.start();
		} catch (BundleException e) {
			LOG.error("Could not start bundle '" + bundle.getLocation() + "'.", e);
		}
	}

	public static Bundle installBundleFrom(InputStream bundleInputStream, String location, BundleContext bundleContext) {
		try {
			return bundleContext.installBundle(location, bundleInputStream);
		} catch (BundleException e) {
			throw new CpbiException("Could not install bundle '" + location + "'.", e);
		}
	}

	public static InputStream createEmptyBundleAround(Manifest manifest) {
		try {
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			JarOutputStream jarOutputStream = new JarOutputStream(outputStream, manifest);
			jarOutputStream.close();
			return new ByteArrayInputStream(outputStream.toByteArray());
		} catch (IOException e) {
			throw new CpbiException("Could not create bundle around mainfest: '" + new BundleManifest(manifest) + "'.",
					e);
		}
	}

	public static Bundle getSystemBundleFrom(BundleContext bundleContext) {
		return bundleContext.getBundle(0);
	}

	public static Set aggregatePackageNamesEffectivelyImportedBy(Bundle bundle, BundleContext bundleContext) {
		Set importedPackages = getPackageNamesDirectlyImportedBy(bundle);
		Bundle[] fragments = getPackageAdminFrom(bundleContext).getFragments(bundle);
		if (fragments != null) {
			for (int i = 0; i < fragments.length; i++) {
				importedPackages.addAll(getPackageNamesDirectlyImportedBy(fragments[i]));
			}
		}
		return importedPackages;
	}

	public static Set getPackageNamesDirectlyImportedBy(Bundle bundle) {
		Set importedPackages = parseRawPackageNamesFrom(getValueOf(Constants.IMPORT_PACKAGE, bundle));
		importedPackages.addAll(parseRawPackageNamesFrom(getValueOf(Constants.DYNAMICIMPORT_PACKAGE, bundle)));
		return importedPackages;
	}

	public static Set parseRawPackageNamesFrom(String packageHeaderValue) {
		if (packageHeaderValue == null) return Collections.EMPTY_SET;
		Set rawPackages = new HashSet();
		int index = 0;
		boolean currentlyIntoAttributes = false;
		boolean currentlyInsideQuotedAttributeValue = false;
		StringBuffer currentPackage = new StringBuffer(256);
		while (index < packageHeaderValue.length()) {
			char currentChar = packageHeaderValue.charAt(index);
			if (currentChar == ATTRIBUTE_DELIMITER) currentlyIntoAttributes = true;
			if (currentChar == '"') currentlyInsideQuotedAttributeValue = !currentlyInsideQuotedAttributeValue;
			if (!currentlyIntoAttributes) {
				if (currentChar == PACKAGE_DELIMITER && !currentlyInsideQuotedAttributeValue) {
					rawPackages.add(currentPackage.toString());
					currentPackage = new StringBuffer(256);
					currentlyIntoAttributes = false;
				} else {
					currentPackage.append(currentChar);
				}
			}
			index++;
		}
		return rawPackages;
	}

	public static String getValueOf(String headerKey, Bundle bundle) {
		return bundle.getHeaders().get(headerKey).toString();
	}

	public static PackageAdmin getPackageAdminFrom(BundleContext bundleContext) {
		ServiceReference serviceReference = bundleContext.getServiceReference(PackageAdmin.class.getName());
		return (PackageAdmin) bundleContext.getService(serviceReference);
	}
}
